package com.imooc.filter;

import com.google.gson.Gson;
import com.imooc.base.BaseInfoProperties;

import com.imooc.grace.result.GraceJSONResult;
import com.imooc.grace.result.ResponseStatusEnum;
import com.imooc.utils.IPUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 全局登录过滤器
 * 通过 @Component 注解实现自动注册，使得 IPLimitFilterJWT 类被 Spring IoC 容器管理，并作为全局过滤器（GlobalFilter）生效，getOrder()返回生效顺序
 */
@Component
@Slf4j
public class IPLimitFilterJWT extends BaseInfoProperties implements GlobalFilter, Ordered {

    @Autowired
    private ExcludeUrlPathProperties excludeUrlPathProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Value("${blackIP.continueCounts}")
    private Integer continueCounts;
    @Value("${blackIP.timeInterval}")
    private Integer timeInterval;
    @Value("${blackIP.limitTimes}")
    private Integer limitTimes;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 1. 获得当前请求的url
        String url = exchange.getRequest().getURI().getPath();

        // 2. 获取所有需要进行ip限流校验的URL
        List<String> ipLimitList = excludeUrlPathProperties.getIpLimitUrls();

        if (ipLimitList != null && !ipLimitList.isEmpty()) {
            for (String limitUrl : ipLimitList) {
                if (antPathMatcher.match(limitUrl, url)) {
                    // 匹配到，需要进行ip拦截校验
                    log.info("IPLimitFilter - 拦截需要进行ip限流校验的url" + url);

                    return doFilter(exchange, chain);
                }
            }
        }

        // 没匹配到，默认放行
        return chain.filter(exchange);
    }

    private Mono<Void> doFilter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 根据request获取当前的请求ip
        ServerHttpRequest request = exchange.getRequest();
        String ip = IPUtil.getIP(request);

        /**
         * 需求：
         * 判断ip在20秒内请求的次数是否超过3次
         * 如果超过，则限制访问30秒
         * 等待30秒静默以后，才能够回复访问
         */
        final String ipRedisKey = "gateway-ip:" + ip;
        final String ipRedisLimitKey = "gateway-ip:limit:" + ip;

        long limitLeftTimes = redis.ttl(ipRedisLimitKey);
        if (limitLeftTimes > 0) {
            //  拦截请求，返回错误信息
            return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_ZUUL);
        }

        long requestCounts = redis.increment(ipRedisKey, 1);
        // 如果是初次访问，需要设置过期时间
        if (requestCounts == 1) {
            redis.expire(ipRedisKey, timeInterval);
        }

        // 如果超限，禁止访问
        if (requestCounts > continueCounts) {
            redis.set(ipRedisLimitKey, ipRedisLimitKey, limitTimes);
            return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_ZUUL);
        }

        return chain.filter(exchange);
    }

    /**
     * 重新包装并返回错误信息
     */
    private Mono<Void> renderErrorMsg(ServerWebExchange exchange, ResponseStatusEnum responseStatusEnum) {
        // 1. 获得response
        ServerHttpResponse response = exchange.getResponse();
        // 2. 构建jsonResult
        GraceJSONResult graceJSONResult = GraceJSONResult.exception(responseStatusEnum);
        // 3. 修改response的code为500
        response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        // 4. 设定header类型
        if (!response.getHeaders().containsKey("Content-Type")) {
            response.getHeaders().add("Content-Type", MimeTypeUtils.APPLICATION_JSON_VALUE);
        }
        // 5. 转换json并且向response中写入数据
        String resultJson = new Gson().toJson(graceJSONResult);
        DataBuffer dataBuffer = response.bufferFactory().wrap(resultJson.getBytes(StandardCharsets.UTF_8));

        return response.writeWith(Mono.just(dataBuffer));
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
